package com.pds.common.utils.minio;

import com.pds.common.core.domain.entity.MinioFile;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.text.DecimalFormat;
import java.util.*;

@Slf4j
@Component
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties({MinIoProperties.class})
public class MinioUtils implements InitializingBean {

    @Autowired
    private MinIoProperties minIoProperties;
    private MinioClient client;


    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.hasText(minIoProperties.getEndpoint(), "Minio url 为空");
        Assert.hasText(minIoProperties.getAccessKey(), "Minio accessKey为空");
        Assert.hasText(minIoProperties.getSecretKey(), "Minio secretKey为空");
        this.client = MinioClient.builder().endpoint(minIoProperties.getEndpoint()).credentials(minIoProperties.getAccessKey(), minIoProperties.getSecretKey()).build();
    }


    /**
     * 验证bucketName是否存在
     *
     * @return boolean true:存在
     */
    public  boolean bucketExists(String bucketName) throws Exception {
        return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     */
    public void createBucket(String bucketName) throws Exception {
        if (!this.bucketExists(bucketName)){
            client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取全部bucket
     * <p>
     * https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
     */
    @SneakyThrows
    public List<Bucket> getAllBuckets() {
        return client.listBuckets();
    }

    /**
     * 根据bucketName获取信息
     *
     * @param bucketName bucket名称
     */
    @SneakyThrows
    public Optional<Bucket> getBucket(String bucketName) {
        return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }


    /**
     * 根据文件前置查询文件
     * @param prefix     前缀
     * @return MinioItem 列表
     */
    @SneakyThrows
    public List getAllObjectsByPrefix( String prefix,String deptId) {
        boolean recursive=false;
        String bucketName=deptId;
        List<MinioFile> list = new ArrayList<>();

        Iterable<Result<Item>> objectsIterator = client.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(transItemToMinioFile(item));
            }
        }
        return list;
    }


    /**
     * 获取文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        return client.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 删除文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#removeObject
     */
    public void removeObject(String bucketName, String objectName) throws Exception {
        client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }


    /**
     * 通过MultipartFile，上传文件
     *
     * @param file 文件
     * @param objectName 对象名
     */
    public  String putObject(MultipartFile file, String objectName, String bucketName)  {
        InputStream inputStream=null;
        try{
            //如果bucketName不存在则先创建
            this.createBucket(bucketName);
            inputStream = file.getInputStream();
            String contentType=getContentType(objectName);
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("Content-Length", String.valueOf(inputStream.available()));
            log.debug("inputsize:"+inputStream.available()+"----fileSize:"+file.getSize());
            client .putObject(PutObjectArgs.builder().bucket(bucketName).headers(headers).object(objectName).stream(inputStream, inputStream.available(), -1).contentType(contentType).build());
            return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build());
        }catch (Exception e){
            log.error("通过MultipartFile，上传文件异常",e);
            return null;
        }finally {
            try {
                if (null!=inputStream){
                    inputStream.close();
                }
            } catch (IOException e) {
                log.error("inputStream关闭异常",e);
            }
        }
    }

    public String readableFileSize(long size) {
        if(size <= 0) return "0";
        final String[] units = new String[] { "B", "kB", "MB", "GB", "TB" };
        int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    /**
     * 下载文件时获取ContentType
     *
     * @param fileName 文件名中包含后缀即可
     */
    public  String getContentType(String fileName) {
        Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(fileName);
        if (mediaType == null) {
            return null;
        }
        return mediaType.orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
    }

    public MinioFile transItemToMinioFile (Item item){
        MinioFile minioFile=new MinioFile();
        try {
            minioFile.setSaveName(URLDecoder.decode(item.objectName(), "utf-8"));
            minioFile.setPath(URLDecoder.decode(getPathFromSavename(item.objectName()),"utf-8"));
            minioFile.setSize(readableFileSize(item.size()));
            minioFile.setFileType(getFileTypeFromSavename(item.objectName()));
            minioFile.setFolder(item.isDir());
            if (!item.isDir()){
                minioFile.setRealName(URLDecoder.decode(getRealnameFromSavename(item.objectName()), "utf-8"));
                try {
                    minioFile.setModifyTime(DateFormatUtils.format(Date.from(item.lastModified().toInstant()), "yyyy-MM-dd HH:mm:ss"));
                } catch (Exception e) {
                    log.error("lastModified修改时间时间转换异常",e);
                }
            }else{
                minioFile.setRealName(URLDecoder.decode(getFolderRealnameFromSavename(item.objectName()), "utf-8"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return minioFile;
    }


    public String getFileTypeFromSavename(String saveName){
        if(saveName.contains(".")){
            int index = saveName.lastIndexOf(".");
            return saveName.substring((index+1),saveName.length());
        }else {
            return saveName;
        }
    }

    public String  getRealnameFromSavename(String saveName){
        if (saveName.contains("/")){
            int index = saveName.lastIndexOf("/");
            return (saveName.substring((index+1),saveName.length()));
        }else {
            return saveName;
        }

    }

    public String  getPathFromSavename(String saveName){
        if (saveName.contains("/")){
            int index = saveName.lastIndexOf("/");
            return (saveName.substring(0,(index+1)));
        }else {
            return "/";
        }
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件夹名称（去掉/）
     * @return true：存在
     */
    public boolean doesFolderExist(String bucketName, String objectName) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = client.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName.substring(0,objectName.length()-1)).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }


    /**
     * 创建文件夹或目录
     *
     * @param bucketName 存储桶
     * @param objectName 目录路径
     */
    public  ObjectWriteResponse putDirObject(String bucketName, String objectName) throws Exception {
        return client.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                                new ByteArrayInputStream(new byte[]{}), 0, -1)
                        .build());
    }

    public List<DeleteError>  deleteFiles(String bucketName , List<String> objectNames, StringBuffer strbuff) throws Exception{
        List<DeleteObject> objects = new LinkedList<>();
        List<DeleteError> deleteErrors = new ArrayList<DeleteError>();
        if (!CollectionUtils.isEmpty(objectNames)){
            for (String objectName: objectNames) {
//                if(objectName.endsWith("/")){
//                    List<MinioFile> minioFiles = getAllObjectsByPrefix(objectName,bucketName);
//                    if(minioFiles.size()>0){
//                        ErrorResponse error = new ErrorResponse(ErrorCode errorCode, String bucketName, String objectName, String resource, String requestId, String hostId);
//                        deleteErrors.add((DeleteError)error);
//                    }
//                }
                objects.add(new DeleteObject(objectName));
            }
        }
        Iterable<Result<DeleteError>> results =
                client.removeObjects(
                        RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            deleteErrors.add(error);
            strbuff.append(error.objectName()).append("-").append(error.message()).append(";");
            log.error("Error in deleting object " + error.objectName() + "; " + error.message());
        }
        return deleteErrors;
    }

    public String  getFolderRealnameFromSavename(String saveName) {
        if (saveName.contains("/")) {
            String[] strings = saveName.split("/");
            return (strings[strings.length - 1]);
        } else {
            return saveName;
        }
    }


    /**
     * 通过MultipartFile，上传文件并返回文件链接
     *
     * @param file 文件
     * @param objectName 对象名
     */
    public  ObjectWriteResponse putObjectReturnUrl(MultipartFile file, String objectName, String bucketName)  {
        InputStream inputStream=null;
        try{
            //如果bucketName不存在则先创建
            this.createBucket(bucketName);
            inputStream = file.getInputStream();
            String contentType=getContentType(objectName);
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Length", String.valueOf(inputStream.available()));
            return client.putObject(PutObjectArgs.builder().bucket(bucketName).headers(headers).object(objectName).stream(inputStream, inputStream.available(), -1).contentType(contentType).build());
        }catch (Exception e){
            log.error("通过MultipartFile上传文件并返回文件链接，上传文件异常",e);
            return null;
        }finally {
            try {
                if (null!=inputStream){
                    inputStream.close();
                }
            } catch (IOException e) {
                log.error("inputStream关闭异常",e);
            }
        }
    }
}
